#include "gigedeviceobject.h"
#include "gigeccdintf.h"
#include "detector_global.h"
#include "../../ImageAcquisitor/global.h"

#include <QFile>
#include <QSettings>
#include <QCoreApplication>

GigEDeviceObject::GigEDeviceObject(QObject *parent) :
    devConnected(-1),
    devLosted(false),
    firstRun(true),
    frameBuf(0),
    darkBuf(0),
    gain30Buf(0),
    gain60Buf(0),
    intf(0),
    DetectorObject(parent)
{
    connect(this, SIGNAL(imageReadyGigE(ushort)), this, SLOT(onImageReadyGigE(ushort)));
    connect(this, SIGNAL(devLostedGigE(ushort,ulong)), this, SLOT(onDevLostedGigE(ushort,ulong)));
}

void GigEDeviceObject::initDetector()
{
    QSettings s(QCoreApplication::applicationDirPath() + "/"PLUGIN_DETECTOR_CONFIG, QSettings::IniFormat);
    s.setIniCodec(QSETTINGS_CODEC_NAME);

    if (!intf) intf = new GigeCcdIntf(s.value(DETECTOR_GENERAL_ACQLIB).toString());
    if (!intf->isNormal()) {
        emit deviceStatus(DetInitFailed, tr("Load image acquisit library failed: ") + intf->errorString());
        return;
    }

    if (devConnected >= 0 && (!devLosted)) {
        intf->MVC_CloseDevice(devConnected);
        devConnected = -1;
    }

    if (!firstRun) {
        intf->MVC_ReScanDevice();
        firstRun = false;
    }

    emit deviceStatus(DetInitializing);

    QSettings cal(QCoreApplication::applicationDirPath() + "/"PLUGIN_DETECTOR_CAL_CONFIG, QSettings::IniFormat);
    cal.setIniCodec(QSETTINGS_CODEC_NAME);

    int w = cal.value(CALIBRATION_FRAME_WIDTH).toInt();
    int h = cal.value(CALIBRATION_FRAME_HEIGHT).toInt();
    int imgSize = w * h;
    if (!darkBuf) {
        darkBuf = new float[imgSize];
        QFile darkFile(CALIBRATION_DARK_FILENAME);
        if ((!darkFile.open(QIODevice::ReadOnly)) ||
                (darkFile.read((char*)darkBuf, imgSize*sizeof(float)) != imgSize*sizeof(float))) {
            emit deviceStatus(DetInitializing, tr("Load dark correction file failed."));
            delete[] darkBuf;
            darkBuf = 0;
        }
        darkFile.close();
    }

    if (!gain30Buf) {
        gain30Buf = new float[imgSize];
        QFile gain30File(CALIBRATION_GAIN30_FILENAME);
        if ((!gain30File.open(QIODevice::ReadOnly)) ||
                (gain30File.read((char*)gain30Buf, imgSize*sizeof(float)) != imgSize*sizeof(float))) {
            emit deviceStatus(DetInitializing, tr("Load 30% gain correction file failed."));
            delete[] gain30Buf;
            gain30Buf = 0;
        }
        gain30File.close();
    }

    if (!gain60Buf) {
        gain60Buf = new float[imgSize];
        QFile gain60File(CALIBRATION_GAIN60_FILENAME);
        if ((!gain60File.open(QIODevice::ReadOnly)) ||
                (gain60File.read((char*)gain60Buf, imgSize*sizeof(float)) != imgSize*sizeof(float))) {
            emit deviceStatus(DetInitializing, tr("Load 60% gain correction file failed."));
            delete[] gain60Buf;
            gain60Buf = 0;
        }
        gain60File.close();
    }

    QString msg;
    int DevNum = intf->MVC_GetDeviceNumber();
    if(DevNum == 0)
    {
        msg = tr("No GEV device found, Check IP config or network.");
        emit deviceStatus(DetInitFailed, msg);
        freeDetector();
    }
    else
    {
        msg = tr("GigE device found:\n");
        for(int j=0; j<DevNum; j++)
        {
            MVCGE_DEVLISTEX DevInfo;
            intf->MVC_GetDeviceInfoEx(j,&DevInfo);
            msg += QString("No.%1: %2 MAC: %3 IP: %4\n").arg(
                              QString::number(j),
                              DevInfo.DevName,
                              DevInfo.DevMAC,
                              DevInfo.DevIP);
        }
        emit deviceStatus(DetInitializing, msg);
    }

    for (int i = 0; i < DevNum; ++i) {
        emit deviceStatus(DetInitializing, tr("Connecting to device No.%1...").arg(i));
        intf->MVC_AutoIPConfig(i);
        if(intf->MVC_OpenDevice(i) == CY_RESULT_OK)
        {
            intf->MVC_SetParameter(i, MVCADJ_SHUTTERTYPE, 1);
            intf->MVC_SetParameter(i, MVCADJ_WIDTH, w);
            intf->MVC_SetParameter(i, MVCADJ_HEIGHT, h);
            intf->MVC_SetParameter(i, MVCADJ_BITMODE, cal.value(CALIBRATION_FRAME_DEPTH).toInt());
            intf->MVC_SetParameter(i, MVCADJ_XOFFSET, cal.value(CALIBRATION_OFFSET_X).toInt());
            intf->MVC_SetParameter(i, MVCADJ_YOFFSET, cal.value(CALIBRATION_OFFSET_Y).toInt());
            intf->MVC_SetParameter(i, MVCL_LINEVALID_EDGE, 1);
            intf->MVC_SetParameter(i, MVCL_FRAMEVALID_EDGE, 1);
            devConnected = i;
            emit deviceStatus(DetInited, tr("Connected to device No.%1.").arg(i));
            break;
        }
        emit deviceStatus(DetInitializing, tr("Connect to device No.%1 failed.").arg(i));
    }

    if (devConnected < 0) {
        emit deviceStatus(DetInitFailed);
        //freeDetector();
    }
}

void GigEDeviceObject::freeDetector()
{
    clearImageBuf();
    delete[] frameBuf;
    frameBuf = 0;
    delete[] darkBuf;
    darkBuf = 0;
    delete[] gain30Buf;
    gain30Buf = 0;
    delete[] gain60Buf;
    gain60Buf = 0;
    if (intf && intf->isNormal() && devConnected >= 0 && (!devLosted)) {
        intf->MVC_CloseDevice(devConnected);
        devConnected = -1;
    }
    firstRun = true;
    delete intf;
    intf = 0;
    emit deviceStatus(DetFreed);
}

void GigEDeviceObject::startCapture(bool start)
{
    clearImageBuf();

    if (!intf) initDetector();
    if (!intf) {
        emit deviceStatus(DetCaptureStartFailed, tr("Detecor not initialized."));
        return;
    }
    if (!intf->isNormal()) {
        emit deviceStatus(DetCaptureStartFailed, intf->errorString());
        return;
    }

    if (start) {
        if (devConnected >= 0 && (!devLosted)) {
            intf->MVC_EnableCapture(devConnected);
            intf->MVC_SetStreamHOOK(devConnected, StreamProc, this);
            intf->MVC_SetDevLostHOOK(devConnected, DevLostProc, this);


            QSettings cal(QCoreApplication::applicationDirPath() + "/"PLUGIN_DETECTOR_CAL_CONFIG, QSettings::IniFormat);
            cal.setIniCodec(QSETTINGS_CODEC_NAME);
            int w = cal.value(CALIBRATION_FRAME_WIDTH).toInt();
            int h = cal.value(CALIBRATION_FRAME_HEIGHT).toInt();
            delete[] frameBuf;
            frameBuf = new ushort[w * h];
            emit deviceStatus(DetExposureReady);
        } else {
            emit deviceStatus(DetCaptureStartFailed, tr("Detector not initialized."));
        }
    } else {
        if (devConnected >= 0 && (!devLosted)) {
            intf->MVC_SetStreamHOOK(devConnected, NULL, this);
            intf->MVC_SetDevLostHOOK(devConnected, NULL, this);
            intf->MVC_DisableCapture(devConnected);
        }
        delete[] frameBuf;
        frameBuf = 0;
        emit deviceStatus(DetCaptureAborted);
    }
}

uint GigEDeviceObject::StreamProc(ushort cardNo, MVCFRAMEINFO frame, void *pData)
{
    GigEDeviceObject *gigeDev = qobject_cast<GigEDeviceObject*>((QObject*)pData);
    if (gigeDev) {
        gigeDev->setImageReadyGigE(cardNo, frame);
        return 1;
    }
    return 0;
}

uint GigEDeviceObject::DevLostProc(ushort cardNo, ulong devStat, void *pData)
{
    GigEDeviceObject *gigeDev = qobject_cast<GigEDeviceObject*>((QObject*)pData);
    if (gigeDev) {
        gigeDev->setDevLostedGigE(cardNo, devStat);
        return 1;
    }
    return 0;
}

void GigEDeviceObject::setImageReadyGigE(ushort cardNo, const MVCFRAMEINFO &frame)
{
    memcpy((uchar*)frameBuf, frame.lBufPtr, frame.lBufSize);
    emit imageReadyGigE(cardNo);
}

void GigEDeviceObject::onDevLostedGigE(ushort cardNo, ulong devStat)
{
    devLosted = true;
    emit deviceStatus(DetDisconnected, tr("No.%1 device losted connection.").arg(cardNo));
}

void GigEDeviceObject::onImageReadyGigE(ushort cardNo)
{

    QSettings cal(QCoreApplication::applicationDirPath() + "/"PLUGIN_DETECTOR_CAL_CONFIG, QSettings::IniFormat);
    cal.setIniCodec(QSETTINGS_CODEC_NAME);

    int w = cal.value(CALIBRATION_FRAME_WIDTH).toInt();
    int h = cal.value(CALIBRATION_FRAME_HEIGHT).toInt();
    int imgSize = w * h;

    if (darkBuf) {
        // Dark correction
        emit deviceStatus(DetCapturing, tr("Doing dark correction..."));
        for (int i = 0; i < imgSize; ++i) {
            frameBuf[i] -= darkBuf[i];
        }
    } else {
        emit deviceStatus(DetCapturing, tr("Dark correction ignored."));
    }

    switch (gainType) {
    case NoGainCorr:
        break;
    case Gain30Corr:
        if (gain30Buf) {
            // Gain correction
            emit deviceStatus(DetCapturing, tr("Doing 30% gain correction..."));
            for (int i = 0; i < imgSize; ++i) {
                frameBuf[i] *= gain30Buf[i];
            }
        } else {
            emit deviceStatus(DetCapturing, tr("30% gain correction ignored."));
        }
        break;
    case Gain60Corr:
        if (gain60Buf) {
            // Gain correction
            emit deviceStatus(DetCapturing, tr("Doing 60% gain correction..."));
            for (int i = 0; i < imgSize; ++i) {
                frameBuf[i] *= gain60Buf[i];
            }
        } else {
            emit deviceStatus(DetCapturing, tr("60% gain correction ignored."));
        }
        break;
    }

    // Bad line correction
    QList<QPair<int, int> > lines;
    QString str = cal.value(CALIBRATION_BADLINE_BADLINES).toString();
    QStringList pairs = str.split(" ");
    foreach (const QString &p, pairs) {
        QStringList ps = p.split(",");
        if (ps.size() == 2) {
            lines += QPair<int, int>(ps.at(0).toInt(), ps.at(1).toInt());
        }
    }

    QPair<int, int> line;
    foreach (line, lines) {
        if (line.first == 0 && line.second < w-1) {
            for (int y = 0; y < h; ++y) {
                int idx = y*w;
                for (int i = line.first; i < line.second; ++i) {
                    frameBuf[idx+i] = frameBuf[idx+line.second+1];
                }
            }
        } else if (line.second == w && line.first > 0) {
            for (int y = 0; y < h; ++y) {
                int idx = y*w;
                for (int i = line.first; i < line.second; ++i) {
                    frameBuf[idx+i] = frameBuf[idx+line.first-1];
                }
            }
        } else if (line.first > 0 && line.second < w-1) {
            int weight = line.second - line.first + 2;
            for (int y = 1; y < h-1; ++y) {
                int idx = y*w;
                uint left = frameBuf[idx-w+line.first-1];
                left += frameBuf[idx+line.first-1];
                left += frameBuf[idx+w+line.first-1];
                uint right = frameBuf[idx-w+line.second+1];
                right += frameBuf[idx+line.second+1];
                right += frameBuf[idx+w+line.second+1];
                for (int i = line.first; i < line.second+1; ++i) {
                    frameBuf[idx+i] = (left*(weight-(i-line.first+1)) + right*(i-line.first+1))/(weight*3);
                }
            }
        }
    }

    // Field correction
    int fw = cal.value(CALIBRATION_FIELD_WIDTH).toInt();
    int fh = cal.value(CALIBRATION_FIELD_HEIGHT).toInt();
    int fx = cal.value(CALIBRATION_FIELD_X).toInt();
    int fy = cal.value(CALIBRATION_FIELD_Y).toInt();
    if ((w > fw && h >= fh) || (w >= fw && h > fh)) {
        emit deviceStatus(DetCapturing, tr("Cropping frame..."));
        imgBuf = new ushort[fw * fh];
        for (int y = 0; y < fh; ++y) {
            int imgIdx = y * fw;
            int frameIdx = (y + fy) * w;
            for (int x = 0; x < fw; ++x) {
                imgBuf[imgIdx+x] = frameBuf[frameIdx+fx+x];
            }
        }
        delete[] frameBuf;
        frameBuf = 0;
        imgWidth = fw;
        imgHeight = fh;
    } else {
        imgBuf = frameBuf;
        frameBuf = 0;
        imgWidth = w;
        imgHeight = h;
    }

    if (cal.value(CALIBRATION_BADPIXEL_FILTER).toBool()) {
        emit deviceStatus(DetCapturing, tr("Filtering random noise...."));
        int width = imgWidth;
        int height = imgHeight;
        ushort threshold = cal.value(CALIBRATION_BADPIXEL_THRESHOLD).toInt();
        uint total, mean, maxVal;
        for (int y = 1; y < height-1; ++y) {
            int idx = y * width;
            for (int x = 1; x < width-1; ++x) {
                total = imgBuf[idx-width+x-1];
                maxVal = imgBuf[idx-width+x-1];
                total += imgBuf[idx-width+x];
                maxVal = maxVal>imgBuf[idx-width+x]?maxVal:imgBuf[idx-width+x];
                total += imgBuf[idx-width+x+1];
                maxVal = maxVal>imgBuf[idx-width+x+1]?maxVal:imgBuf[idx-width+x+1];
                total += imgBuf[idx+x-1];
                maxVal = maxVal>imgBuf[idx+x-1]?maxVal:imgBuf[idx+x-1];
                total += imgBuf[idx+x+1];
                maxVal = maxVal>imgBuf[idx+x+1]?maxVal:imgBuf[idx+x+1];
                total += imgBuf[idx+width+x-1];
                maxVal = maxVal>imgBuf[idx+width+x-1]?maxVal:imgBuf[idx+width+x-1];
                total += imgBuf[idx+width+x];
                maxVal = maxVal>imgBuf[idx+width+x]?maxVal:imgBuf[idx+width+x];
                total += imgBuf[idx+width+x+1];
                maxVal = maxVal>imgBuf[idx+width+x+1]?maxVal:imgBuf[idx+width+x+1];
                mean = (total - maxVal) / 7;
                if ((mean>imgBuf[idx+x] && (mean-imgBuf[idx+x]>threshold)) ||
                        (mean<imgBuf[idx+x] && (imgBuf[idx+x]-mean>threshold))) {
                    imgBuf[idx+x] = mean;
                }
            }
        }
    }

    emit deviceStatus(DetImageReady, tr("No.%1 device image ready.").arg(cardNo));
}
